#!/usr/bin/env python

# found at http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
# some import form http://pypi.python.org/pypi/daemon/1.0

# imported from
# $Id: process_detail.py 1236 2011-12-13 19:00:35Z g.rodola $
#
# Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import sys, os
from signal import SIGINT
from utils import Logged, config, GAnswer
from glog import GLog
from manager import GAction

def convert_bytes(n):
	symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
	prefix = {}
	for i, s in enumerate(symbols):
		prefix[s] = 1 << (i+1)*10
	for s in reversed(symbols):
		if n >= prefix[s]:
			value = float(n) / prefix[s]
			return '%.1f%s' % (value, s)
	return "%sB" % n

class GMain(GAction):
	name = 'main'
	live = True
	daemonize = False

	def __init__(self):
		pass

	def start(self):
		if self.checkPID() == False:
			return False

		# Check for a pidfile to see if the daemon already runs
		try:
			pf = file(self.pid_file, 'r')
			pid = int(pf.read().strip())
			pf.close()
		except IOError:
			pid = None
	
		if pid:
			self.log_error("pidfile %s already exist. Daemon already running?")
			return False

		try:
			os.makedirs(self.work_dir)
		except Exception:
			pass

		# Start the daemon
		if self.daemonize:
			self.daemon()

		# create work_dir and pid_file
		self.writePID()

		# unload threading for avoid: Exception KeyError: KeyError(...,) in <module 'threading' from ...
		if 'threading' in sys.modules:
			del(sys.modules['threading']) 

		# patch gevent important this should be done after deamonize
		from gevent import monkey ; monkey.patch_all(aggressive = False)

		from gevent import signal
		from gevent.pool import Pool

		signal(SIGINT, self.signal_handler)

		l = GLog()
		if not self.daemonize:
			l.add()

		if self.debug:
			l.debug = True

		if os.path.exists(self.log_file):
			if self.old_log == 'copy':
				import time
				try:
					os.makedirs(self.work_dir + '/old_logs')
				except Exception:
					pass
				t = time.localtime()
				s = '%.4i%.2i%.2i%.2i%.2i%.2i' % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
				os.rename(self.log_file, '%s/old_logs/%s_log.txt' % (self.work_dir, s))
			elif self.old_log == 'clean':
				open(self.log_file, 'w').close()

		l.add(self.log_file, False)

		self.pool = Pool(self.pool_size)
		from setproctitle import setproctitle
		setproctitle(self.instance)
		config.pool = self.pool
		GLog().init(self.pool, False, config)

		if self.web_port is not None:
			from http import WebServer
			web = WebServer('', self.web_port)
			self.pool.spawn(web.serve_forever)
			config.o['web'] = web

		if self.xmlrpc_port is not None:
			from manager import XmlRPCManager
			xmlrpc = XmlRPCManager(self.pool, self.xmlrpc_host, port = self.xmlrpc_port, name = 'xman')
			self.pool.spawn(xmlrpc.serve_forever)
			config.o['xman'] = xmlrpc

		config.o['log'] = GLog()
		from manager import GManager
		if self.socket is not None: 
			config.o['zmq'] = GManager(self.pool, self.socket)

		GAction.__init__(self, pool = self.pool, code = self.code)
		self.run()

		# and here we GO!!!
		self.pool.join()

		if os.path.exists(self.pid_file):
			os.remove(self.pid_file)

		sys.exit(0)

	def stop(self):
		self.log('stop')
		for i in config.o:
    			if i != 'main' and i != 'log' and hasattr(config.o[i], 'stop'):
				self.log('stopping %s' % i)
				config.o[i].stop()
		self.log('stopping log')
		config.o['log'].stop()
		try:
			self.pool.kill()
		except Exception, e:
			pass

	def signal_handler(self):
		self.stop()

	def reset(self):
		self.live = True
		os.kill(self.pid, SIGINT)
	
	def daemon(self):
		"""
		do the UNIX double-fork magic, see Stevens' "Advanced 
		Programming in the UNIX Environment" for details (ISBN 0201563177)
		http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
		"""
		from gevent import fork
		try:
			pid = fork()
			if pid > 0:
				# exit first parent
				sys.exit(0) 
		except OSError, e: 
			sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
			sys.exit(1)
	
		# decouple from parent environment
		os.chdir(self.work_dir) 
		os.setsid()
		os.umask(0)
	
		# do second fork
		try: 
			pid = fork() 
			if pid > 0:
				# exit from second parent 
				print 'Started process PID: %i' % pid
				sys.exit(0)
		except OSError, e:
			sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
			sys.exit(1)

	def writePID(self):
		# write pidfile
		self.pid = os.getpid()
		file(self.pid_file,'w+').write("%d\n" % self.pid)

	def checkPID(self):
		pid = None
		if os.path.exists(self.pid_file):
			try:
				pid = int(open(self.pid_file).read())
			except ValueError:
				sys.exit('Pidfile %s contains non-numeric value' % self.pid_file)
				return False

			try:
				os.kill(pid, 0)
			except OSError, why:
				import errno
				if why[0] == errno.ESRCH:
					# The pid doesnt exists.
					print('Removing stale pidfile %s' % self.pid_file)
					os.remove(self.pid_file)
					return True
				else:
					sys.exit("Can't check status of PID %s from pidfile %s: %s" % (pid, self.pid_file, why[1]))
					return False

			sys.exit("Another server is running, PID %s\n" %  pid)
			return  False
		return True
	
	def status(self):
		import psutil, datetime
		p = psutil.Process(self.pid)
		started = datetime.datetime.fromtimestamp(p.create_time).strftime('%Y-%m-%d %H:%M')
		mem = p.get_memory_info()
		mem = '%s%% (resident=%s, virtual=%s) ' % (round(p.get_memory_percent(), 1),
							  convert_bytes(mem.rss),
							  convert_bytes(mem.vms))
		
		s = '\n%s version: %s' % (config.get_pkg('name'), config.get_pkg('version'))
		s += '\nname: %s (%d)\nstarted: %s\nuser: %s\nmem: %s' % (p.name, p.pid, started, p.username, mem) 
		s += '\ncpu: %.2f%%' % (GLog().cpu_usage)
		s += '\nnum threads: %d' % p.get_num_threads()
		s += '\npool: %s/%s' % (self.pool_size - self.pool.free_count(), self.pool_size)

		files = p.get_open_files()
		if files:
			s += '\n\nopen files:'
			for file in files:
				s += '\nfd=%s %s ' % (file.fd, file.path)

		connections = p.get_connections()
		if connections:
			s += '\n\nopen connections:'
			import socket
			for conn in connections:
				if conn.type == socket.SOCK_STREAM:
					type = 'TCP'
				elif conn.type == socket.SOCK_DGRAM:
					type = 'UDP'
				else:
					type = 'UNIX'
				lip, lport = conn.local_address
				if not conn.remote_address:
					rip, rport = '*', '*'
				else:
					rip, rport = conn.remote_address
				s += '\n%s:%s -> %s:%s type=%s status=%s' % (lip, lport, rip, rport, type, conn.status)

		# since GIL sucks
		#if threads:
		#	self.log('running threads')
		#	for thread in threads:
		#		self.log('id=%s, user-time=%s, sys-time=%s' %(thread.id, thread.user_time, thread.system_time))
		
		children = p.get_children()
		if children:
			s += '\n\nchildren:'
			for child in children:
				s += 'pid=%s name=%s' % (child.pid, child.name)
		

		self.log(s)
		return GAnswer(True, load = s)

	def readPidFile(self):
		pid = None
		if os.path.exists(self.pid_file):
			try:
				pid = int(open(self.pid_file).read())
			except ValueError:
				print 'Pidfile %s contains non-numeric value' % self.pid_file
		return pid

	def kill_pid_from_pidfile(self):
		pid = self.readPidFile()
		from signal import SIGINT
		if pid is not None:
			try:
				os.kill(pid, SIGINT)
			except Exception, e:
				print e
			
			from gevent import sleep

			# wait for removing pid_file 
			while self.readPidFile() is not None:
				sleep(0.1)

			# for sure
			try:
				os.kill(pid, 9)
			except Exception, e:
				pass
			print 'PID: %i killed' % pid
		else:
			print 'Process not run - do nothing'
		

